{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "JMio44BDpYtw"
   },
   "source": [
    "<font color=\"red\">注</font>: 使用 tensorboard 可视化需要安装 tensorflow (TensorBoard依赖于tensorflow库，可以任意安装tensorflow的gpu/cpu版本)\n",
    "\n",
    "```shell\n",
    "pip install tensorflow-cpu\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T07:49:33.225136Z",
     "start_time": "2024-07-22T07:49:29.446561200Z"
    },
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "execution": {
     "iopub.execute_input": "2025-01-22T12:52:07.458253Z",
     "iopub.status.busy": "2025-01-22T12:52:07.457903Z",
     "iopub.status.idle": "2025-01-22T12:52:09.718181Z",
     "shell.execute_reply": "2025-01-22T12:52:09.717587Z",
     "shell.execute_reply.started": "2025-01-22T12:52:07.458217Z"
    },
    "id": "YMWskinNpYtz",
    "outputId": "841d6519-0a89-4563-cfb9-6c0d79361cd7",
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/local/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=10, micro=14, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cu124\n",
      "cuda:0\n"
     ]
    }
   ],
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "\n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n",
    "\n",
    "seed = 42\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "execution": {
     "iopub.execute_input": "2025-01-22T12:52:12.110237Z",
     "iopub.status.busy": "2025-01-22T12:52:12.109817Z",
     "iopub.status.idle": "2025-01-22T12:52:16.713959Z",
     "shell.execute_reply": "2025-01-22T12:52:16.713316Z",
     "shell.execute_reply.started": "2025-01-22T12:52:12.110212Z"
    },
    "id": "olzoueSyqofw",
    "outputId": "afd2617d-aafb-4d94-fbaa-d56fd122f22e",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Looking in indexes: https://mirrors.cloud.aliyuncs.com/pypi/simple\n",
      "Requirement already satisfied: kaggle in /usr/local/lib/python3.10/site-packages (1.6.17)\n",
      "Requirement already satisfied: six>=1.10 in /usr/local/lib/python3.10/site-packages (from kaggle) (1.17.0)\n",
      "Requirement already satisfied: certifi>=2023.7.22 in /usr/local/lib/python3.10/site-packages (from kaggle) (2024.12.14)\n",
      "Requirement already satisfied: python-dateutil in /usr/local/lib/python3.10/site-packages (from kaggle) (2.9.0.post0)\n",
      "Requirement already satisfied: requests in /usr/local/lib/python3.10/site-packages (from kaggle) (2.32.3)\n",
      "Requirement already satisfied: tqdm in /usr/local/lib/python3.10/site-packages (from kaggle) (4.67.1)\n",
      "Requirement already satisfied: python-slugify in /usr/local/lib/python3.10/site-packages (from kaggle) (8.0.4)\n",
      "Requirement already satisfied: urllib3 in /usr/local/lib/python3.10/site-packages (from kaggle) (2.3.0)\n",
      "Requirement already satisfied: bleach in /usr/local/lib/python3.10/site-packages (from kaggle) (6.2.0)\n",
      "Requirement already satisfied: webencodings in /usr/local/lib/python3.10/site-packages (from bleach->kaggle) (0.5.1)\n",
      "Requirement already satisfied: text-unidecode>=1.3 in /usr/local/lib/python3.10/site-packages (from python-slugify->kaggle) (1.3)\n",
      "Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/site-packages (from requests->kaggle) (3.4.1)\n",
      "Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/site-packages (from requests->kaggle) (3.10)\n",
      "\u001b[33mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv\u001b[0m\u001b[33m\n",
      "\u001b[0m\n",
      "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.3.2\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m24.3.1\u001b[0m\n",
      "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n"
     ]
    }
   ],
   "source": [
    "!pip install kaggle"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-22T12:52:19.705111Z",
     "iopub.status.busy": "2025-01-22T12:52:19.704746Z",
     "iopub.status.idle": "2025-01-22T12:52:19.819676Z",
     "shell.execute_reply": "2025-01-22T12:52:19.819014Z",
     "shell.execute_reply.started": "2025-01-22T12:52:19.705086Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "/mnt/workspace/chapter_6\n"
     ]
    }
   ],
   "source": [
    "!pwd"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "ExecutionIndicator": {
     "show": false
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T02:01:14.100470Z",
     "iopub.status.busy": "2025-01-21T02:01:14.100150Z",
     "iopub.status.idle": "2025-01-21T02:01:14.214430Z",
     "shell.execute_reply": "2025-01-21T02:01:14.213878Z",
     "shell.execute_reply.started": "2025-01-21T02:01:14.100449Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "slothkong\n"
     ]
    }
   ],
   "source": [
    "!rm -r /content/datasets/"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-22T12:56:46.317820Z",
     "iopub.status.busy": "2025-01-22T12:56:46.317343Z",
     "iopub.status.idle": "2025-01-22T12:56:46.321048Z",
     "shell.execute_reply": "2025-01-22T12:56:46.320557Z",
     "shell.execute_reply.started": "2025-01-22T12:56:46.317796Z"
    },
    "id": "L8O3V3yJqbWH",
    "tags": []
   },
   "outputs": [],
   "source": [
    "import json\n",
    "token = {\"username\":\"heartheart\",\"key\":\"19f40dbd10ba3a3baeb37c061408d48f\"}\n",
    "with open('/mnt/workspace/chapter_6/kaggle.json', 'w') as file:\n",
    "  json.dump(token, file)#json.dump类似于write"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-22T12:58:24.961736Z",
     "iopub.status.busy": "2025-01-22T12:58:24.961361Z",
     "iopub.status.idle": "2025-01-22T12:58:25.077284Z",
     "shell.execute_reply": "2025-01-22T12:58:25.076712Z",
     "shell.execute_reply.started": "2025-01-22T12:58:24.961712Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{\"username\": \"heartheart\", \"key\": \"19f40dbd10ba3a3baeb37c061408d48f\"}"
     ]
    }
   ],
   "source": [
    "!cat /mnt/workspace/chapter_6/kaggle.json"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "execution": {
     "iopub.execute_input": "2025-01-22T12:58:45.771892Z",
     "iopub.status.busy": "2025-01-22T12:58:45.771520Z",
     "iopub.status.idle": "2025-01-22T12:58:46.518923Z",
     "shell.execute_reply": "2025-01-22T12:58:46.518324Z",
     "shell.execute_reply.started": "2025-01-22T12:58:45.771866Z"
    },
    "id": "Pf8ZxoKzqgzu",
    "outputId": "0face2c5-be84-4ed2-f295-70cd2401708f",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "- path is now set to: /content\n"
     ]
    }
   ],
   "source": [
    "!mkdir -p ~/.kaggle\n",
    "!cp /mnt/workspace/chapter_6/kaggle.json ~/.kaggle/\n",
    "!chmod 600 ~/.kaggle/kaggle.json\n",
    "!kaggle config set -n path -v /content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "execution": {
     "iopub.execute_input": "2025-01-22T13:19:17.558863Z",
     "iopub.status.busy": "2025-01-22T13:19:17.558496Z",
     "iopub.status.idle": "2025-01-22T13:20:55.977717Z",
     "shell.execute_reply": "2025-01-22T13:20:55.977086Z",
     "shell.execute_reply.started": "2025-01-22T13:19:17.558837Z"
    },
    "id": "B8hQi6ejqkAG",
    "outputId": "774e8927-4da0-492d-d7c1-cc39c774dfe3",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Dataset URL: https://www.kaggle.com/datasets/slothkong/10-monkey-species\n",
      "License(s): CC0-1.0\n",
      "Downloading 10-monkey-species.zip to /content/datasets/slothkong/10-monkey-species\n",
      "... resuming from 379584512 bytes (194034810 bytes left) ...\n",
      "100%|███████████████████████████████████████▉| 547M/547M [01:35<00:00, 16.7MB/s]\n",
      "100%|████████████████████████████████████████| 547M/547M [01:35<00:00, 2.03MB/s]\n"
     ]
    }
   ],
   "source": [
    "!kaggle datasets download -d slothkong/10-monkey-species"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "execution": {
     "iopub.execute_input": "2025-01-22T13:22:35.570988Z",
     "iopub.status.busy": "2025-01-22T13:22:35.570596Z",
     "iopub.status.idle": "2025-01-22T13:22:39.187113Z",
     "shell.execute_reply": "2025-01-22T13:22:39.186409Z",
     "shell.execute_reply.started": "2025-01-22T13:22:35.570963Z"
    },
    "id": "tporD6JYsFQ-",
    "outputId": "c264f15e-db74-4cd8-f68f-c347c83b438b",
    "tags": []
   },
   "outputs": [],
   "source": [
    "!unzip -o -d /content /content/datasets/slothkong/10-monkey-species/10-monkey-species.zip >/dev/null"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "NPVrMdFjpYt1"
   },
   "source": [
    "## 数据准备\n",
    "\n",
    "```shell\n",
    "$ tree -L 2 archive\n",
    "archive\n",
    "├── monkey_labels.txt\n",
    "├── training\n",
    "│   ├── n0\n",
    "│   ├── n1\n",
    "│   ├── n2\n",
    "│   ├── n3\n",
    "│   ├── n4\n",
    "│   ├── n5\n",
    "│   ├── n6\n",
    "│   ├── n7\n",
    "│   ├── n8\n",
    "│   └── n9\n",
    "└── validation\n",
    "    ├── n0\n",
    "    ├── n1\n",
    "    ├── n2\n",
    "    ├── n3\n",
    "    ├── n4\n",
    "    ├── n5\n",
    "    ├── n6\n",
    "    ├── n7\n",
    "    ├── n8\n",
    "    └── n9\n",
    "\n",
    "22 directories, 1 file\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-22T13:22:50.068811Z",
     "iopub.status.busy": "2025-01-22T13:22:50.068399Z",
     "iopub.status.idle": "2025-01-22T13:22:50.188620Z",
     "shell.execute_reply": "2025-01-22T13:22:50.187898Z",
     "shell.execute_reply.started": "2025-01-22T13:22:50.068781Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "01-classification_model-cnn.ipynb\n",
      "02_classification_model-cnn-selu.ipynb\n",
      "03_classification_model-separable_cnn.ipynb\n",
      "04_10_monkeys_model_1_冲突文件_Administrator_20240815203110.ipynb\n",
      "04_10_monkeys_model_1_aliyun.ipynb\n",
      "04_10_monkeys_model_1.ipynb\n",
      "05_10_monkeys_model_2_resnet50_finetune_冲突文件_Administrator_20240815203110.ipynb\n",
      "05_10_monkeys_model_2_resnet50_finetune.ipynb\n",
      "06_cifar10_model_1_冲突文件_Administrator_20240815203110.ipynb\n",
      "06_cifar10_model_1.ipynb\n",
      "archive\n",
      "data\n",
      "kaggle.json\n"
     ]
    }
   ],
   "source": [
    "!ls"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T08:52:17.543980900Z",
     "start_time": "2024-07-22T08:52:17.520817400Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "execution": {
     "iopub.execute_input": "2025-01-22T13:23:03.164493Z",
     "iopub.status.busy": "2025-01-22T13:23:03.164093Z",
     "iopub.status.idle": "2025-01-22T13:23:05.582689Z",
     "shell.execute_reply": "2025-01-22T13:23:05.582077Z",
     "shell.execute_reply.started": "2025-01-22T13:23:03.164466Z"
    },
    "id": "nnayCtXTpYt2",
    "outputId": "0055a8d0-ec63-43bd-c02c-378b3e571921",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "load 1097 images from training dataset\n",
      "load 272 images from validation dataset\n"
     ]
    }
   ],
   "source": [
    "from torchvision import datasets\n",
    "from torchvision.transforms import ToTensor, Resize, Compose, ConvertImageDtype, Normalize\n",
    "\n",
    "\n",
    "from pathlib import Path\n",
    "\n",
    "DATA_DIR1 = Path(\"/content/training/\")\n",
    "DATA_DIR2 = Path(\"/content/validation/\")\n",
    "\n",
    "class MonkeyDataset(datasets.ImageFolder):\n",
    "    def __init__(self, mode, transform=None):\n",
    "        if mode == \"train\":\n",
    "            root = DATA_DIR1 / \"training\"\n",
    "        elif mode == \"val\":\n",
    "            root = DATA_DIR2 / \"validation\"\n",
    "        else:\n",
    "            raise ValueError(\"mode should be one of the following: train, val, but got {}\".format(mode))\n",
    "        super().__init__(root, transform) # 调用父类init方法\n",
    "        self.imgs = self.samples # self.samples里边是图片路径及标签 [(path, label), (path, label),...]\n",
    "        self.targets = [s[1] for s in self.samples] # 标签取出来\n",
    "\n",
    "# 预先设定的图片尺寸\n",
    "img_h, img_w = 128, 128\n",
    "transform = Compose([\n",
    "    Resize((img_h, img_w)), # 图片缩放\n",
    "    ToTensor(),\n",
    "    # 预先统计的\n",
    "    Normalize([0.4363, 0.4328, 0.3291], [0.2085, 0.2032, 0.1988]),\n",
    "    ConvertImageDtype(torch.float), # 转换为float类型\n",
    "]) #数据预处理\n",
    "\n",
    "\n",
    "train_ds = MonkeyDataset(\"train\", transform=transform)\n",
    "val_ds = MonkeyDataset(\"val\", transform=transform)\n",
    "\n",
    "print(\"load {} images from training dataset\".format(len(train_ds)))\n",
    "print(\"load {} images from validation dataset\".format(len(val_ds)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T08:15:15.286096100Z",
     "start_time": "2024-07-22T08:15:15.272778500Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-22T13:23:20.457957Z",
     "iopub.status.busy": "2025-01-22T13:23:20.457479Z",
     "iopub.status.idle": "2025-01-22T13:23:20.462708Z",
     "shell.execute_reply": "2025-01-22T13:23:20.462198Z",
     "shell.execute_reply.started": "2025-01-22T13:23:20.457932Z"
    },
    "id": "oS2jYFvMpYt3",
    "outputId": "837dd61e-a898-4fa8-9d5d-51adcf2b2b47",
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['n0', 'n1', 'n2', 'n3', 'n4', 'n5', 'n6', 'n7', 'n8', 'n9']"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 数据类别\n",
    "train_ds.classes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T08:15:24.968412700Z",
     "start_time": "2024-07-22T08:15:24.960415300Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-22T13:23:23.222859Z",
     "iopub.status.busy": "2025-01-22T13:23:23.222355Z",
     "iopub.status.idle": "2025-01-22T13:23:23.227214Z",
     "shell.execute_reply": "2025-01-22T13:23:23.226532Z",
     "shell.execute_reply.started": "2025-01-22T13:23:23.222835Z"
    },
    "id": "8SJAYnQopYt4",
    "outputId": "15f39953-ca91-40e4-d084-e5e8c1d4667c",
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'n0': 0,\n",
       " 'n1': 1,\n",
       " 'n2': 2,\n",
       " 'n3': 3,\n",
       " 'n4': 4,\n",
       " 'n5': 5,\n",
       " 'n6': 6,\n",
       " 'n7': 7,\n",
       " 'n8': 8,\n",
       " 'n9': 9}"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "train_ds.class_to_idx"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T08:22:41.089921600Z",
     "start_time": "2024-07-22T08:22:41.076096400Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-22T13:23:24.739377Z",
     "iopub.status.busy": "2025-01-22T13:23:24.739003Z",
     "iopub.status.idle": "2025-01-22T13:23:24.820103Z",
     "shell.execute_reply": "2025-01-22T13:23:24.819372Z",
     "shell.execute_reply.started": "2025-01-22T13:23:24.739354Z"
    },
    "id": "o2x2L3yTpYt4",
    "outputId": "6925790a-4125-4882-9d51-d015fbd6e7eb",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "/content/training/training/n0/n0018.jpg 0\n",
      "tensor([[[-0.1553, -0.0236,  0.3525,  ...,  1.1425,  1.8384,  0.9920],\n",
      "         [-0.1365, -0.1365,  0.1833,  ...,  0.8416,  1.9324,  0.9356],\n",
      "         [-0.1177, -0.1741,  0.1268,  ...,  1.1425,  1.3306,  1.3494],\n",
      "         ...,\n",
      "         [ 2.1205,  2.1205,  2.0641,  ...,  0.9920,  0.8416,  0.3713],\n",
      "         [ 2.0641,  1.9136,  1.6691,  ...,  0.3713, -0.0612, -0.0048],\n",
      "         [ 1.9513,  1.6691,  1.2553,  ...,  0.0328, -0.4938, -0.1365]],\n",
      "\n",
      "        [[-0.2193, -0.0456,  0.3983,  ...,  1.3632,  2.0580,  1.0737],\n",
      "         [-0.2386, -0.2000,  0.2053,  ...,  0.9386,  2.1545,  1.1702],\n",
      "         [-0.2772, -0.2772,  0.0702,  ...,  1.1702,  1.5176,  1.6527],\n",
      "         ...,\n",
      "         [ 2.4054,  2.3668,  2.2703,  ...,  0.6491,  0.5333,  0.1088],\n",
      "         [ 2.2896,  2.1352,  1.8650,  ...,  0.0509, -0.3351, -0.3544],\n",
      "         [ 2.1352,  1.8457,  1.4211,  ..., -0.3544, -0.8369, -0.5667]],\n",
      "\n",
      "        [[-0.7283, -0.4324,  0.2975,  ...,  0.2383,  0.9287, -0.3141],\n",
      "         [-0.7678, -0.6297,  0.0805,  ...,  0.1002,  1.1851, -0.3338],\n",
      "         [-0.8467, -0.7480, -0.0971,  ...,  0.3961,  0.8103,  0.6131],\n",
      "         ...,\n",
      "         [-0.4324, -0.4916, -0.6099,  ...,  0.5934,  0.4553,  0.0213],\n",
      "         [-0.5113, -0.6099, -0.8467,  ..., -0.0379, -0.5508, -0.5508],\n",
      "         [-0.6494, -0.8467, -1.1228,  ..., -0.5705, -1.2215, -0.9650]]]) 0\n"
     ]
    }
   ],
   "source": [
    "# 图片路径 及 标签\n",
    "for fpath, label in train_ds.imgs:\n",
    "    print(fpath, label)\n",
    "    break\n",
    "\n",
    "#这个和之前的dataset完全一致\n",
    "for img, label in train_ds:\n",
    "    # c, h, w  label\n",
    "    print(img, label)\n",
    "    break"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T08:44:58.447431700Z",
     "start_time": "2024-07-22T08:44:29.539230100Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-22T13:23:57.810650Z",
     "iopub.status.busy": "2025-01-22T13:23:57.810278Z",
     "iopub.status.idle": "2025-01-22T13:24:16.122125Z",
     "shell.execute_reply": "2025-01-22T13:24:16.121468Z",
     "shell.execute_reply.started": "2025-01-22T13:23:57.810626Z"
    },
    "id": "mVb-3y68pYt5",
    "outputId": "3116d220-7fb4-4b1d-d648-baed26152c4f",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(tensor([0.0002]), tensor([0.9999]))\n"
     ]
    }
   ],
   "source": [
    "def cal_mean_std(ds):\n",
    "    mean = 0.\n",
    "    std = 0.\n",
    "    for img, _ in ds:\n",
    "        mean += img[0:1, :, :].mean(dim=(1, 2))\n",
    "        std += img[0:1, :, :].std(dim=(1, 2))\n",
    "    mean /= len(ds)\n",
    "    std /= len(ds)\n",
    "    return mean, std\n",
    "\n",
    "# 经过 normalize 后 均值为0，方差为1\n",
    "print(cal_mean_std(train_ds))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T08:32:54.269143900Z",
     "start_time": "2024-07-22T08:32:25.590376800Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-20T12:00:46.039218Z",
     "iopub.status.busy": "2025-01-20T12:00:46.038848Z",
     "iopub.status.idle": "2025-01-20T12:01:01.867120Z",
     "shell.execute_reply": "2025-01-20T12:01:01.866643Z",
     "shell.execute_reply.started": "2025-01-20T12:00:46.039196Z"
    },
    "id": "ARxVczNhpYt5",
    "outputId": "b2b2706e-4c8d-494d-f317-195bc1b55ddc"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(tensor([3.6267e-05]), tensor([0.9999]))\n"
     ]
    }
   ],
   "source": [
    "def cal_mean_std(ds):\n",
    "    mean = 0.\n",
    "    std = 0.\n",
    "    for img, _ in ds:\n",
    "        mean += img[1:2, :, :].mean(dim=(1, 2))\n",
    "        std += img[1:2, :, :].std(dim=(1, 2))\n",
    "    mean /= len(ds)\n",
    "    std /= len(ds)\n",
    "    return mean, std\n",
    "\n",
    "# 经过 normalize 后 均值为0，方差为1\n",
    "print(cal_mean_std(train_ds))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T08:33:54.095864500Z",
     "start_time": "2024-07-22T08:33:26.014587300Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-20T12:01:01.867906Z",
     "iopub.status.busy": "2025-01-20T12:01:01.867688Z",
     "iopub.status.idle": "2025-01-20T12:01:17.736340Z",
     "shell.execute_reply": "2025-01-20T12:01:17.735739Z",
     "shell.execute_reply.started": "2025-01-20T12:01:01.867889Z"
    },
    "id": "kdtwsOxIpYt5",
    "outputId": "e723ef22-4061-4795-e9c1-4c85bcb2df67"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(tensor([-6.5391e-07]), tensor([1.0002]))\n"
     ]
    }
   ],
   "source": [
    "def cal_mean_std(ds):\n",
    "    mean = 0.\n",
    "    std = 0.\n",
    "    for img, _ in ds:\n",
    "        mean += img[2:3, :, :].mean(dim=(1, 2))\n",
    "        std += img[2:3, :, :].std(dim=(1, 2))\n",
    "    mean /= len(ds)\n",
    "    std /= len(ds)\n",
    "    return mean, std\n",
    "\n",
    "# 经过 normalize 后 均值为0，方差为1\n",
    "print(cal_mean_std(train_ds))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T08:39:44.080468500Z",
     "start_time": "2024-07-22T08:39:15.435622600Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-22T13:24:25.349385Z",
     "iopub.status.busy": "2025-01-22T13:24:25.349039Z",
     "iopub.status.idle": "2025-01-22T13:24:25.353605Z",
     "shell.execute_reply": "2025-01-22T13:24:25.352899Z",
     "shell.execute_reply.started": "2025-01-22T13:24:25.349363Z"
    },
    "id": "2U0E5JMTpYt6",
    "outputId": "ab841753-c1d0-4dfd-cd6d-375ac31e02d6",
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 遍历train_ds得到每张图片，计算每个通道的均值和方差\n",
    "def cal_mean_std(ds):\n",
    "    mean = 0.\n",
    "    std = 0.\n",
    "    for img, _ in ds:\n",
    "        mean += img.mean(dim=(1, 2)) #dim=(1, 2)表示计算均值后，宽和高消除（把宽和高所有的像素加起来，再除以总数）\n",
    "        std += img.std(dim=(1, 2))\n",
    "    mean /= len(ds)\n",
    "    std /= len(ds)\n",
    "    return mean, std\n",
    "\n",
    "# 经过 normalize 后 均值为0，方差为1\n",
    "# print(cal_mean_std(train_ds))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T08:52:57.696309200Z",
     "start_time": "2024-07-22T08:52:57.686315700Z"
    },
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "execution": {
     "iopub.execute_input": "2025-01-22T13:24:26.979748Z",
     "iopub.status.busy": "2025-01-22T13:24:26.979341Z",
     "iopub.status.idle": "2025-01-22T13:24:26.983890Z",
     "shell.execute_reply": "2025-01-22T13:24:26.983249Z",
     "shell.execute_reply.started": "2025-01-22T13:24:26.979722Z"
    },
    "id": "vzPl-CPOpYt6",
    "outputId": "51436c68-37f1-42c0-ad44-a5582069bcc1",
    "tags": []
   },
   "outputs": [],
   "source": [
    "import torch.nn as nn\n",
    "from torch.utils.data.dataloader import DataLoader\n",
    "\n",
    "batch_size = 64\n",
    "# 从数据集到dataloader\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True, num_workers=4)\n",
    "val_loader = DataLoader(val_ds, batch_size=batch_size, shuffle=False, num_workers=4)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2024-07-22T08:53:04.933884100Z",
     "start_time": "2024-07-22T08:52:59.755852600Z"
    },
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "execution": {
     "iopub.execute_input": "2025-01-22T13:24:28.670813Z",
     "iopub.status.busy": "2025-01-22T13:24:28.670448Z",
     "iopub.status.idle": "2025-01-22T13:24:31.435440Z",
     "shell.execute_reply": "2025-01-22T13:24:31.434702Z",
     "shell.execute_reply.started": "2025-01-22T13:24:28.670789Z"
    },
    "id": "PvzJPmCKpYt7",
    "outputId": "14c12450-f807-4fc7-f521-fba2d5427f88",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([64, 3, 128, 128])\n",
      "torch.Size([64])\n"
     ]
    }
   ],
   "source": [
    "for imgs, labels in train_loader:\n",
    "    print(imgs.shape)\n",
    "    print(labels.shape)\n",
    "    break"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "MxR9Zm8FpYt7"
   },
   "source": [
    "## 定义模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-22T13:25:06.483548Z",
     "iopub.status.busy": "2025-01-22T13:25:06.483111Z",
     "iopub.status.idle": "2025-01-22T13:25:06.559692Z",
     "shell.execute_reply": "2025-01-22T13:25:06.559047Z",
     "shell.execute_reply.started": "2025-01-22T13:25:06.483519Z"
    },
    "id": "93Z27pjxpYt7",
    "outputId": "5795e337-36fb-4dc0-b9c5-9c94061d524f",
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "conv1.weight\tparamerters num: 864\n",
      "conv1.bias\tparamerters num: 32\n",
      "conv2.weight\tparamerters num: 9216\n",
      "conv2.bias\tparamerters num: 32\n",
      "conv3.weight\tparamerters num: 18432\n",
      "conv3.bias\tparamerters num: 64\n",
      "conv4.weight\tparamerters num: 36864\n",
      "conv4.bias\tparamerters num: 64\n",
      "conv5.weight\tparamerters num: 73728\n",
      "conv5.bias\tparamerters num: 128\n",
      "conv6.weight\tparamerters num: 147456\n",
      "conv6.bias\tparamerters num: 128\n",
      "fc1.weight\tparamerters num: 4194304\n",
      "fc1.bias\tparamerters num: 128\n",
      "fc2.weight\tparamerters num: 1280\n",
      "fc2.bias\tparamerters num: 10\n"
     ]
    }
   ],
   "source": [
    "\n",
    "class CNN(nn.Module):\n",
    "    def __init__(self, num_classes=10, activation=\"relu\"):\n",
    "        super(CNN, self).__init__()\n",
    "        self.activation = F.relu if activation == \"relu\" else F.selu\n",
    "        self.conv1 = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, padding=\"same\")\n",
    "        self.conv2 = nn.Conv2d(in_channels=32, out_channels=32, kernel_size=3, padding=\"same\")\n",
    "        self.pool = nn.MaxPool2d(2, 2)\n",
    "        self.conv3 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, padding=\"same\")\n",
    "        self.conv4 = nn.Conv2d(in_channels=64, out_channels=64, kernel_size=3, padding=\"same\")\n",
    "        self.conv5 = nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, padding=\"same\")\n",
    "        self.conv6 = nn.Conv2d(in_channels=128, out_channels=128, kernel_size=3, padding=\"same\")\n",
    "        self.flatten = nn.Flatten()\n",
    "        # input shape is (3, 128, 128) so the flatten output shape is 128 * 16 * 16\n",
    "        self.fc1 = nn.Linear(128 * 16 * 16, 128)\n",
    "        self.fc2 = nn.Linear(128, num_classes)\n",
    "\n",
    "        self.init_weights()\n",
    "\n",
    "    def init_weights(self):\n",
    "        \"\"\"使用 xavier 均匀分布来初始化全连接层、卷积层的权重 W\"\"\"\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, (nn.Linear, nn.Conv2d)):\n",
    "                nn.init.xavier_uniform_(m.weight)\n",
    "                nn.init.zeros_(m.bias)\n",
    "\n",
    "    def forward(self, x):\n",
    "        act = self.activation\n",
    "        x = self.pool(act(self.conv2(act(self.conv1(x)))))\n",
    "        x = self.pool(act(self.conv4(act(self.conv3(x)))))\n",
    "        x = self.pool(act(self.conv6(act(self.conv5(x)))))\n",
    "        x = self.flatten(x)\n",
    "        x = act(self.fc1(x))\n",
    "        x = self.fc2(x)\n",
    "        return x\n",
    "\n",
    "\n",
    "for idx, (key, value) in enumerate(CNN().named_parameters()):\n",
    "    print(f\"{key}\\tparamerters num: {np.prod(value.shape)}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "1qNGeGLmpYt8"
   },
   "source": [
    "## 训练\n",
    "\n",
    "pytorch的训练需要自行实现，包括\n",
    "1. 定义损失函数\n",
    "2. 定义优化器\n",
    "3. 定义训练步\n",
    "4. 训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-22T13:25:09.514795Z",
     "iopub.status.busy": "2025-01-22T13:25:09.514396Z",
     "iopub.status.idle": "2025-01-22T13:25:09.627095Z",
     "shell.execute_reply": "2025-01-22T13:25:09.626479Z",
     "shell.execute_reply.started": "2025-01-22T13:25:09.514770Z"
    },
    "id": "mz4gW8hupYt8",
    "tags": []
   },
   "outputs": [],
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    pred_list = []\n",
    "    label_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)         # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "\n",
    "        preds = logits.argmax(axis=-1)    # 验证集预测\n",
    "        pred_list.extend(preds.cpu().numpy().tolist())\n",
    "        label_list.extend(labels.cpu().numpy().tolist())\n",
    "\n",
    "    acc = accuracy_score(label_list, pred_list)\n",
    "    return np.mean(loss_list), acc\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "vK16Ii8KpYt8",
    "tags": []
   },
   "source": [
    "### TensorBoard 可视化\n",
    "\n",
    "\n",
    "训练过程中可以使用如下命令启动tensorboard服务。\n",
    "\n",
    "```shell\n",
    "tensorboard \\\n",
    "    --logdir=runs \\     # log 存放路径\n",
    "    --host 0.0.0.0 \\    # ip\n",
    "    --port 8848         # 端口\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-22T13:26:49.575321Z",
     "iopub.status.busy": "2025-01-22T13:26:49.574963Z",
     "iopub.status.idle": "2025-01-22T13:26:49.778310Z",
     "shell.execute_reply": "2025-01-22T13:26:49.777680Z",
     "shell.execute_reply.started": "2025-01-22T13:26:49.575297Z"
    },
    "id": "0RaJusY7pYt9",
    "tags": []
   },
   "outputs": [],
   "source": [
    "from torch.utils.tensorboard import SummaryWriter\n",
    "\n",
    "\n",
    "class TensorBoardCallback:\n",
    "    def __init__(self, log_dir, flush_secs=10):\n",
    "        \"\"\"\n",
    "        Args:\n",
    "            log_dir (str): dir to write log.\n",
    "            flush_secs (int, optional): write to dsk each flush_secs seconds. Defaults to 10.\n",
    "        \"\"\"\n",
    "        self.writer = SummaryWriter(log_dir=log_dir, flush_secs=flush_secs)\n",
    "\n",
    "    def draw_model(self, model, input_shape):\n",
    "        self.writer.add_graph(model, input_to_model=torch.randn(input_shape))\n",
    "\n",
    "    def add_loss_scalars(self, step, loss, val_loss):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/loss\",\n",
    "            tag_scalar_dict={\"loss\": loss, \"val_loss\": val_loss},\n",
    "            global_step=step,\n",
    "            )\n",
    "\n",
    "    def add_acc_scalars(self, step, acc, val_acc):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/accuracy\",\n",
    "            tag_scalar_dict={\"accuracy\": acc, \"val_accuracy\": val_acc},\n",
    "            global_step=step,\n",
    "        )\n",
    "\n",
    "    def add_lr_scalars(self, step, learning_rate):\n",
    "        self.writer.add_scalars(\n",
    "            main_tag=\"training/learning_rate\",\n",
    "            tag_scalar_dict={\"learning_rate\": learning_rate},\n",
    "            global_step=step,\n",
    "\n",
    "        )\n",
    "\n",
    "    def __call__(self, step, **kwargs):\n",
    "        # add loss\n",
    "        loss = kwargs.pop(\"loss\", None)\n",
    "        val_loss = kwargs.pop(\"val_loss\", None)\n",
    "        if loss is not None and val_loss is not None:\n",
    "            self.add_loss_scalars(step, loss, val_loss)\n",
    "        # add acc\n",
    "        acc = kwargs.pop(\"acc\", None)\n",
    "        val_acc = kwargs.pop(\"val_acc\", None)\n",
    "        if acc is not None and val_acc is not None:\n",
    "            self.add_acc_scalars(step, acc, val_acc)\n",
    "        # add lr\n",
    "        learning_rate = kwargs.pop(\"lr\", None)\n",
    "        if learning_rate is not None:\n",
    "            self.add_lr_scalars(step, learning_rate)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "RhHZoT43pYt-"
   },
   "source": [
    "### Save Best\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-22T13:25:13.974282Z",
     "iopub.status.busy": "2025-01-22T13:25:13.973754Z",
     "iopub.status.idle": "2025-01-22T13:25:13.980073Z",
     "shell.execute_reply": "2025-01-22T13:25:13.979450Z",
     "shell.execute_reply.started": "2025-01-22T13:25:13.974256Z"
    },
    "id": "X6-gIb2YpYt_",
    "tags": []
   },
   "outputs": [],
   "source": [
    "class SaveCheckpointsCallback:\n",
    "    def __init__(self, save_dir, save_step=5000, save_best_only=True):\n",
    "        \"\"\"\n",
    "        Save checkpoints each save_epoch epoch.\n",
    "        We save checkpoint by epoch in this implementation.\n",
    "        Usually, training scripts with pytorch evaluating model and save checkpoint by step.\n",
    "\n",
    "        Args:\n",
    "            save_dir (str): dir to save checkpoint\n",
    "            save_epoch (int, optional): the frequency to save checkpoint. Defaults to 1.\n",
    "            save_best_only (bool, optional): If True, only save the best model or save each model at every epoch.\n",
    "        \"\"\"\n",
    "        self.save_dir = save_dir\n",
    "        self.save_step = save_step\n",
    "        self.save_best_only = save_best_only\n",
    "        self.best_metrics = -1\n",
    "\n",
    "        # mkdir\n",
    "        if not os.path.exists(self.save_dir):\n",
    "            os.mkdir(self.save_dir)\n",
    "\n",
    "    def __call__(self, step, state_dict, metric=None):\n",
    "        if step % self.save_step > 0:\n",
    "            return\n",
    "\n",
    "        if self.save_best_only:\n",
    "            assert metric is not None\n",
    "            if metric >= self.best_metrics:\n",
    "                # save checkpoints\n",
    "                torch.save(state_dict, os.path.join(self.save_dir, \"best.ckpt\"))\n",
    "                # update best metrics\n",
    "                self.best_metrics = metric\n",
    "        else:\n",
    "            torch.save(state_dict, os.path.join(self.save_dir, f\"{step}.ckpt\"))\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "BtnJ4AlcpYt_"
   },
   "source": [
    "### Early Stop"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-22T13:25:15.707549Z",
     "iopub.status.busy": "2025-01-22T13:25:15.707149Z",
     "iopub.status.idle": "2025-01-22T13:25:15.712379Z",
     "shell.execute_reply": "2025-01-22T13:25:15.711732Z",
     "shell.execute_reply.started": "2025-01-22T13:25:15.707523Z"
    },
    "id": "qLSAWIq8pYuA",
    "tags": []
   },
   "outputs": [],
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute\n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "\n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter\n",
    "            self.counter = 0\n",
    "        else:\n",
    "            self.counter += 1\n",
    "\n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "colab": {
     "referenced_widgets": [
      "481dddc81fe84be3afc1853a2c537f47"
     ]
    },
    "execution": {
     "iopub.execute_input": "2025-01-22T13:26:53.263610Z",
     "iopub.status.busy": "2025-01-22T13:26:53.263005Z"
    },
    "id": "p9T_O6X7pYuA",
    "outputId": "4cd378c8-3792-4cf4-91b4-33d3cbe1e02b",
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " 55%|█████▌    | 198/360 [01:12<01:04,  2.51it/s, epoch=10]"
     ]
    }
   ],
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model,\n",
    "    train_loader,\n",
    "    val_loader,\n",
    "    epoch,\n",
    "    loss_fct,\n",
    "    optimizer,\n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=None,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "\n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas)\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    "                preds = logits.argmax(axis=-1)\n",
    "\n",
    "                acc = accuracy_score(labels.cpu().numpy(), preds.cpu().numpy())\n",
    "                loss = loss.cpu().item()\n",
    "                # record\n",
    "\n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"acc\": acc, \"step\": global_step\n",
    "                })\n",
    "\n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss, val_acc = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"acc\": val_acc, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "\n",
    "                    # 1. 使用 tensorboard 可视化\n",
    "                    if tensorboard_callback is not None:\n",
    "                        tensorboard_callback(\n",
    "                            global_step,\n",
    "                            loss=loss, val_loss=val_loss,\n",
    "                            acc=acc, val_acc=val_acc,\n",
    "                            lr=optimizer.param_groups[0][\"lr\"],\n",
    "                            )\n",
    "\n",
    "                    # 2. 保存模型权重 save model checkpoint\n",
    "                    if save_ckpt_callback is not None:\n",
    "                        save_ckpt_callback(global_step, model.state_dict(), metric=val_acc)\n",
    "\n",
    "                    # 3. 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(val_acc)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "\n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "\n",
    "    return record_dict\n",
    "\n",
    "\n",
    "epoch = 20\n",
    "\n",
    "activation = \"relu\"\n",
    "model = CNN(num_classes=10, activation=activation)\n",
    "\n",
    "# 1. 定义损失函数 采用交叉熵损失\n",
    "loss_fct = nn.CrossEntropyLoss()\n",
    "# 2. 定义优化器 采用 adam\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=0.001, eps=1e-7)\n",
    "\n",
    "# 1. tensorboard 可视化\n",
    "if not os.path.exists(\"runs\"):\n",
    "    os.mkdir(\"runs\")\n",
    "tensorboard_callback = TensorBoardCallback(f\"runs/monkeys-cnn-{activation}\")\n",
    "tensorboard_callback.draw_model(model, [1, 3, img_h, img_w])\n",
    "# 2. save best\n",
    "if not os.path.exists(\"checkpoints\"):\n",
    "    os.makedirs(\"checkpoints\")\n",
    "save_ckpt_callback = SaveCheckpointsCallback(f\"checkpoints/monkeys-cnn-{activation}\", save_step=len(train_loader), save_best_only=True)\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=5)\n",
    "\n",
    "model = model.to(device)\n",
    "record = training(\n",
    "    model,\n",
    "    train_loader,\n",
    "    val_loader,\n",
    "    epoch,\n",
    "    loss_fct,\n",
    "    optimizer,\n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=save_ckpt_callback,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-20T12:03:09.997301Z",
     "iopub.status.busy": "2025-01-20T12:03:09.997072Z",
     "iopub.status.idle": "2025-01-20T12:03:10.211833Z",
     "shell.execute_reply": "2025-01-20T12:03:10.211419Z",
     "shell.execute_reply.started": "2025-01-20T12:03:09.997283Z"
    },
    "id": "m_D2srP_pYuC",
    "outputId": "2f735bb6-bd73-43e1-afb8-2bd472e33d0c"
   },
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 1000x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    fig_num = len(train_df.columns)\n",
    "    fig, axs = plt.subplots(1, fig_num, figsize=(5 * fig_num, 5))\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        axs[idx].plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        axs[idx].plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        axs[idx].grid()\n",
    "        axs[idx].legend()\n",
    "        # axs[idx].set_xticks(range(0, train_df.index[-1], 5000))\n",
    "        # axs[idx].set_xticklabels(map(lambda x: f\"{int(x/1000)}k\", range(0, train_df.index[-1], 5000)))\n",
    "        axs[idx].set_xlabel(\"step\")\n",
    "\n",
    "    plt.show()\n",
    "\n",
    "plot_learning_curves(record, sample_step=10)  #横坐标是 steps"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "l9N2QrfFpYuD"
   },
   "source": [
    "# 评估"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-20T12:03:10.212522Z",
     "iopub.status.busy": "2025-01-20T12:03:10.212313Z",
     "iopub.status.idle": "2025-01-20T12:03:11.741987Z",
     "shell.execute_reply": "2025-01-20T12:03:11.741471Z",
     "shell.execute_reply.started": "2025-01-20T12:03:10.212506Z"
    },
    "id": "qiN3FDcJpYuE",
    "outputId": "9c9b50e4-79d5-4027-824b-8f7ef233eea9",
    "tags": []
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/tmp/ipykernel_251/2960646440.py:4: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.\n",
      "  model.load_state_dict(torch.load(f\"checkpoints/monkeys-cnn-{activation}/best.ckpt\", map_location=\"cpu\"))\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     2.4858\n",
      "accuracy: 0.6176\n"
     ]
    }
   ],
   "source": [
    "# dataload for evaluating\n",
    "\n",
    "# load checkpoints\n",
    "model.load_state_dict(torch.load(f\"checkpoints/monkeys-cnn-{activation}/best.ckpt\", map_location=\"cpu\"))\n",
    "\n",
    "model.eval()\n",
    "loss, acc = evaluating(model, val_loader, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\\naccuracy: {acc:.4f}\")"
   ]
  }
 ],
 "metadata": {
  "colab": {
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
